package prvydevj;

import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.ArrayList;
import java.util.Date;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.basex.core.BaseXException;
import org.basex.core.Context;
import org.basex.core.cmd.CreateDB;
import org.basex.core.cmd.CreateIndex;
import org.basex.core.cmd.Export;
import org.basex.core.cmd.XQuery;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Rozhraní pro komunikaci s XML databází.
 */
public class InvoiceManager {

    private DocumentBuilderFactory docFactory;
    private DocumentBuilder builder;
    private Context context = new Context();
    private String xmlFile;
    DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * Vytvoří spojení s XML souborem
     * @param xmlFile název XML souboru
     * @throws BaseXException
     * @throws DatatypeConfigurationException
     * @throws ParserConfigurationException
     */
    public void Connect(String xmlFile) throws BaseXException, DatatypeConfigurationException, ParserConfigurationException
    {
        docFactory = DocumentBuilderFactory.newInstance();
        
        builder = docFactory.newDocumentBuilder();
            
        new CreateDB("Evidence", xmlFile).execute(context);
        new CreateIndex("fulltext").execute(context);
        this.xmlFile = xmlFile;
    }

    public void setOwnerDetails(String name, String address, String city, String psc, String ico, String dic, String account )
    {
        String osoba =  "<osoba>" +
                        "    <jmeno>"+name+"</jmeno>" +
                        "    <adresa>" +
                        "      <ulice>" +address+ "</ulice>" +
                        "      <mesto>"+city+"</mesto>" +
                        "      <psc>"+psc+"</psc>" +
                        "    </adresa>" +
                        "    <ico>"+ico+"</ico>" +
                        "    <dic>"+dic+"</dic>" +
                        "    <ucet>"+account+"</ucet>" +
                        "</osoba>";

        update(new XQuery("replace node /evidence/osoba with " + osoba));
    }

    public String getOwnerName()
    {
        return execute(new XQuery("for $doc in collection() return $doc/evidence/osoba/jmeno/text()"));
    }

    public String getOwnerAddress()
    {
        return execute(new XQuery("for $doc in collection() return $doc/evidence/osoba/adresa/ulice/text()"));
    }

    public String getOwnerCity()
    {
        return execute(new XQuery("for $doc in collection() return $doc/evidence/osoba/adresa/mesto/text()"));
    }

    public String getOwnerPsc()
    {
        return execute(new XQuery("for $doc in collection() return $doc/evidence/osoba/adresa/psc/text()"));
    }

    public String getOwnerIco()
    {
        return execute(new XQuery("for $doc in collection() return $doc/evidence/osoba/ico/text()"));
    }

    public String getOwnerDic()
    {
        return execute(new XQuery("for $doc in collection() return $doc/evidence/osoba/dic/text()"));
    }

    public String getOwnerAccount()
    {
        return execute(new XQuery("for $doc in collection() return $doc/evidence/osoba/ucet/text()"));
    }


    /**
     * Vrací kolekci všech transakcí v databázi
     * @return kolekce transakcí
     */
    public List<Transaction> getAllTransactions()
    {
        try
        {
            Document doc = getXmlDocument(
                execute(new XQuery("for $doc in collection() return $doc/evidence/faktury")));

            List<Transaction> transactions = new ArrayList<>();
            NodeList nodes = doc.getElementsByTagName("faktura");
            for (int i = 0; i < nodes.getLength(); i++) {
                Element faktura = (Element) nodes.item(i);

                int cislo = Integer.parseInt(faktura.getAttribute("cislo"));
                boolean prijem = faktura.getAttribute("typ").toLowerCase().equals("prijem");

                String ucet = faktura.getElementsByTagName("ucet").item(0).getTextContent();
                String vystaveni = faktura.getElementsByTagName("vystaveni").item(0).getTextContent();
                String splatnost = faktura.getElementsByTagName("splatnost").item(0).getTextContent();

                Date vystaveniDatum = df.parse(vystaveni);
                Date splatnostDatum = df.parse(splatnost);

                Transaction transaction = new Transaction(cislo, vystaveniDatum, splatnostDatum, ucet, prijem);

                Element organizace = (Element) faktura.getElementsByTagName("organizace").item(0);
                String nazev = organizace.getElementsByTagName("nazev").item(0).getTextContent();

                Element adresa = (Element) organizace.getElementsByTagName("adresa").item(0);
                String ulice = adresa.getElementsByTagName("ulice").item(0).getTextContent();
                String mesto = adresa.getElementsByTagName("mesto").item(0).getTextContent();
                String psc = adresa.getElementsByTagName("psc").item(0).getTextContent();

                String ico = organizace.getElementsByTagName("ico").item(0).getTextContent();
                String dic = organizace.getElementsByTagName("dic").item(0).getTextContent();

                transaction.setOrganisation(nazev, ico, dic, mesto, ulice, psc);

                Element polozkyElement = (Element) faktura.getElementsByTagName("polozky").item(0);
                NodeList polozky = polozkyElement.getElementsByTagName("polozka");

                for (int j = 0; j < polozky.getLength(); j++) {
                    Element polozka = (Element) polozky.item(j);
                    String popis = polozka.getAttribute("popis");
                    int pocet = Integer.parseInt(polozka.getAttribute("pocet"));
                    BigDecimal cena = new BigDecimal(polozka.getAttribute("cena"));

                    transaction.addItem(new InvoiceItem(popis, pocet, cena));             
                }

                transactions.add(transaction);
            }

            return transactions;
        }
        catch (ParseException ex)
        {
            System.out.println(ex.getMessage());
            return null;    
        }
    }

    /**
     * Přidání transakce do databáze
     * @param transaction objekt transakce
     */
    public void addTransaction(Transaction transaction)
    {
        Document data = getXmlDocument(execute(new XQuery("for $doc in collection() return $doc/evidence/faktury/*[last()]")));
        NodeList elm = data.getElementsByTagName("faktura");
        if (elm.getLength() == 0)
            transaction.setId(0);
        else
            transaction.setId(Integer.parseInt(((Element)elm.item(0)).getAttribute("cislo")) + 1);

        String faktura = transactionToXml(transaction);

        update(new XQuery("insert node " + faktura + "as last into /evidence/faktury"));
    }

    /**
     * Vykonání XQuery dotazu na databázi
     * @param xquery xquery dotaz
     * @return
     */
    private String execute(XQuery xquery) {
        try {
            return xquery.execute(context);
        } catch (BaseXException ex) {
            throw new RuntimeException("Can't execute database query", ex);
        }
    }

    /**
     * Aktualizace databáze XQuery dotazem
     * @param xquery xquery dotaz
     */
    private void update(XQuery xquery) {
        try {
            xquery.execute(context);
        } catch (BaseXException ex) {
            throw new RuntimeException("Can't execute database query", ex);
        }
        try {
            Export.export(context.data(), xmlFile, null);
        } catch (IOException ex) {
            throw new RuntimeException("Can't save database", ex);
        }
    }

    /**
     * Transformace objektu Transaction na XML
     * @param transaction objekt transakce
     * @return transakce v XML formátu
     */
    private String transactionToXml(Transaction transaction)
    {
        String faktura = "<faktura cislo=\"" + transaction.getId() + "\" typ=\""+ (transaction.getPrijem() ? "prijem" : "vydej") +"\">";
        
        faktura += "<ucet>" + transaction.getUcet() + "</ucet>" +
                   "<vystaveni>" + df.format(transaction.getVystaveni())+ "</vystaveni>" +
                   "<splatnost>" + df.format(transaction.getSplatnost()) + "</splatnost>" +
                   "<polozky>";
                   
        for(InvoiceItem item : transaction.getItems())
        {
            faktura += "<polozka cena=\"" + item.getCena() + "\" pocet=\"" + item.getKs() + "\" popis=\"" + item.getNazev() + "\" />";
        }
        
        faktura += "</polozky>";
        faktura += "<organizace>";
        faktura += "<nazev>" + transaction.getNazev() + "</nazev>";
        faktura += "<adresa><ulice>" + transaction.getUlice()+ "</ulice>" +
                "<mesto>" + transaction.getMesto() + "</mesto><psc>" + transaction.getPsc()  + "</psc></adresa>";
        
        faktura += "<ico>" + transaction.getIco()+ "</ico>";
        faktura += "<dic>" + transaction.getDic()+ "</dic>";
        
        faktura += "</organizace>";
        
        faktura += "</faktura>";
        
        return faktura;
    }
    
    
    private Document getXmlDocument(String data)
    {
        InputSource iS = new InputSource();
        iS.setCharacterStream(new StringReader(data));

        try {
            return builder.parse(iS);
        } catch (SAXException | IOException ex) {
            throw new RuntimeException("Invalid data format", ex);
        }
    }

    /**
     * Výpis všech transakcí v databázi
     * @throws BaseXException chyba při získávání dat z databáze
     */
    public void printAll() throws BaseXException
    {
        List<Transaction> transactions = getAllTransactions();
        
        for (Transaction transaction : transactions) {
            System.out.println(transaction.getUcet());
        }
    }
}

